sce = readr::read_rds("~/results/sub_SZ_AstOliSST.rds")
sce = as(sce, "SingleCellExperiment")
pb.logcounts
class: SingleCellExperiment 
dim: 17658 148 
metadata(3): experiment_info agg_pars n_cells
assays(7): 2 6 ... 25 30
rownames(17658): LINC00115 FAM41C ... AC004556.1 AC240274.1
rowData names(0):
colnames(148): 118847 118848 ... Batch8.HTO7 Batch8.HTO9
colData names(1): group_id
reducedDimNames(0):
altExpNames(0):
pb.logcounts = readRDS(file = "~/PFC_v3/PB_mean_logcounts_renormalized_subtypes.RDS")

meta.data = readRDS("~/PFC_v3/merged_metadata_with_SZTR.rds")

common.ids = intersect(colnames(pb.logcounts), meta.data$Internal_ID)
pb.logcounts = pb.logcounts[, common.ids]
metadata(pb.logcounts)$n_cells = metadata(pb.logcounts)$n_cells[, common.ids]

colData(pb.logcounts) = cbind(colData(pb.logcounts), meta.data)

Filter samples

ncells = metadata(pb.logcounts)$n_cells

mask = (fast_column_sums(ncells) > 100)
pb.logcounts.filtered = pb.logcounts[, mask]
metadata(pb.logcounts.filtered)$n_cells = metadata(pb.logcounts.filtered)$n_cells[, mask]

meta.filtered = meta.data[match(colnames(pb.logcounts.filtered), meta.data$ID), ]
colData(pb.logcounts.filtered) = cbind(colData(pb.logcounts.filtered), DataFrame(meta.filtered))
# Muscat analysis: two cohorts
require(muscat)
require(edgeR)
require(limma)

form = ~ Phenotype + Batch + Gender + Age + PMI + Benzodiazepines + Anticonvulsants + AntipsychTyp + AntipsychAtyp + Antidepress

resDE = lapply( levels(pb.logcounts.filtered$Cohort), function(chrt){

    keep.ids = colnames(pb.logcounts.filtered)[pb.logcounts.filtered$Cohort == chrt]

    pb.logcounts.filtered_sub = pb.logcounts.filtered[,keep.ids]
    metadata(pb.logcounts.filtered_sub)$n_cells = metadata(pb.logcounts.filtered_sub)$n_cells[,keep.ids]

    design.mat <- model.matrix(form, data = droplevels(colData(pb.logcounts.filtered_sub)))

    colnames(design.mat)[1] = c("Intercept")

    contrast.mat <- makeContrasts(contrasts = "PhenotypeSZ", levels = design.mat)

    pbDS(pb.logcounts.filtered_sub, method = "limma-trend", min_cells = 5, design = design.mat, contrast =  contrast.mat, filter = "gene")
})
2..6..16..19..21..25..30..2..6..16..19..21..25..30..
names(resDE) = levels(colData(pb.logcounts.filtered)$Cohort)

readr::write_rds(resDE, "~/PFC_v3/filtered_resDE_subtypes.rds")
celltypes = intersect(names(resDE$McLean$table$PhenotypeSZ), names(resDE$MtSinai$table$PhenotypeSZ))

filtered.tables = lapply(celltypes, function(celltype) {
  tbl1 = resDE[[1]]$table$PhenotypeSZ[[celltype]]
  tbl2 = resDE[[2]]$table$PhenotypeSZ[[celltype]]
  
  genes = intersect(tbl1$gene[1 <= abs(tbl1$logFC/sd(tbl1$logFC))], tbl2$gene[1 <= abs(tbl2$logFC / sd(tbl2$logFC))])
  
  tbl1 = tbl1[match(genes, tbl1$gene), ]
  tbl2 = tbl2[match(genes, tbl2$gene), ]
  
  mask = sign(tbl1$logFC)*sign(tbl2$logFC) > 0
  tbl1 = tbl1[mask, ]
  tbl2 = tbl2[mask, ]
  
  tbls = list(McClean = tbl1, MtSinai = tbl2) 
  tbls = lapply( tbls, function(tab){
    tab$se = tab$logFC / tab$t
    tab
  })
  
  return(tbls)
})
names(filtered.tables) = celltypes

readr::write_rds(filtered.tables, "~/PFC_v3/individual_diff_results_filtered_subtypes.rds")

Export as excel tables

for(ds in 1:2) {

  library(openxlsx)
  Up.wb <- createWorkbook()
  for(i in 1:length(tbls)) {
    res = filtered.tables[[i]][[ds]]
    res = res[res$logFC > 0, ]
    res = cbind(data.frame(Gene = rownames(res)), res)
    res = res[order(res$t, decreasing = T), ]
  
    n = names(filtered.tables)[[i]] #str_replace(arch.names[arch.order[i]], "/", "-")
    
    addWorksheet(wb=Up.wb, sheetName = n)
    writeData(Up.wb, sheet = n, res) 
  
  }
  saveWorkbook(Up.wb, sprintf("~/PFC_v3/DE_genes_up_%s_subtype.xlsx", names(filtered.tables[[i]])[[ds]]), overwrite = TRUE)
  
  library(openxlsx)
  Down.wb <- createWorkbook()
  for(i in 1:length(tbls)) {
    res = filtered.tables[[i]][[ds]]
    res = res[res$logFC < 0, ]
    res = cbind(data.frame(Gene = rownames(res)), res)
    res = res[order(res$t, decreasing = F), ]
    
    n = names(filtered.tables)[[i]] #str_replace(arch.names[arch.order[i]], "/", "-")
    
    addWorksheet(wb=Down.wb, sheetName = n)
    writeData(Down.wb, sheet = n, res) 
  
  }
  saveWorkbook(Down.wb, sprintf("~/PFC_v3/DE_genes_down_%s_subtype.xlsx", names(filtered.tables[[i]])[[ds]]), overwrite = TRUE)
}
combined.analysis.tables = lapply(names(filtered.tables), function(celltype) {
  print(celltype)
  tbls = filtered.tables[[celltype]]
  
  gene.tbls = lapply(1:nrow(tbls[[1]]), function(i) {
    dfs = lapply(1:length(tbls), function(k) tbls[[k]][i, ])
    df = do.call("rbind", dfs)
  })
  names(gene.tbls) = tbls[[1]]$gene
    
  combined.analysis.tbl = do.call(rbind, lapply(names(gene.tbls), function(gene){
    x = suppressWarnings(metafor::rma(yi=logFC, sei=se, data = gene.tbls[[gene]], method="FE"))
    combined.tbl = data.frame( gene = gene, 
        logFC     = x$beta,
        se        = x$se,
        tstat = x$zval,
        P.Value   = x$pval)
    return(combined.tbl)
  }))
  rownames(combined.analysis.tbl) = names(gene.tbls)
  
  combined.analysis.tbl = combined.analysis.tbl[order(combined.analysis.tbl$P.Value), ]
  
  return(combined.analysis.tbl)
})
names(combined.analysis.tables) = names(filtered.tables)

DF = do.call(rbind, combined.analysis.tables)
DF$adj.P.Val = p.adjust(DF$P.Value, "fdr")
combined.analysis.tables = split(DF, unlist(lapply(names(combined.analysis.tables), function(celltype) rep(celltype, nrow(combined.analysis.tables[[celltype]])))))

readr::write_rds(combined.analysis.tables, "~/PFC_v3/meta_analysis_diff_results_subtypes.rds")
resDE = readr::read_rds("~/PFC_v3/filtered_resDE.rds")

filtered.tables = readr::read_rds("~/PFC_v3/individual_diff_results_filtered.rds")

combined.analysis.tables = readr::read_rds("~/PFC_v3/meta_analysis_diff_results.rds")
  library(openxlsx)
  Up.wb <- createWorkbook()
  for(i in 1:length(combined.analysis.tables)) {
    res = combined.analysis.tables[[i]]
    res = res[(res$logFC > 0.1) & (res$P.Value <= 0.05), ]
    res = res[order(res$t, decreasing = T), ]
  
    n = names(combined.analysis.tables)[[i]] #str_replace(arch.names[arch.order[i]], "/", "-")
    
    addWorksheet(wb=Up.wb, sheetName = n)
    writeData(Up.wb, sheet = n, res) 
  
  }
  saveWorkbook(Up.wb, sprintf("~/PFC_v3/DE_genes_up_%s_subtype.xlsx", "combined"), overwrite = TRUE)
  
  
  library(openxlsx)
  Down.wb <- createWorkbook()
  for(i in 1:length(combined.analysis.tables)) {
    res = combined.analysis.tables[[i]]
    res = res[(res$logFC < -0.1) & (res$P.Value <= 0.05), ]
    res = res[order(res$t, decreasing = F), ]
    
    n = names(combined.analysis.tables)[[i]] #str_replace(arch.names[arch.order[i]], "/", "-")
    
    addWorksheet(wb=Down.wb, sheetName = n)
    writeData(Down.wb, sheet = n, res) 
  
  }
  saveWorkbook(Down.wb, sprintf("~/PFC_v3/DE_genes_down_%s_subtype.xlsx", "combined"), overwrite = TRUE)
DE.sc = matrix(0, nrow(pb.logcounts), length(combined.analysis.tables))
tstats = matrix(0, nrow(pb.logcounts), length(combined.analysis.tables))
logFC = matrix(0, nrow(pb.logcounts), length(combined.analysis.tables))
logPvals = matrix(0, nrow(pb.logcounts), length(combined.analysis.tables))
rownames(DE.sc) = rownames(tstats) = rownames(logFC) = rownames(logPvals) = rownames(pb.logcounts)
colnames(DE.sc) = colnames(tstats) = colnames(logFC) = colnames(logPvals) = names(combined.analysis.tables)

limma_trend_mean.scores = matrix(0, nrow(pb.logcounts), length(combined.analysis.tables))
Up.genes = vector("list", length(combined.analysis.tables))
Down.genes = vector("list", length(combined.analysis.tables))
rownames(limma_trend_mean.scores) = rownames(pb.logcounts)
names(Up.genes) = names(Down.genes) = colnames(limma_trend_mean.scores) = names(combined.analysis.tables)
for(i in 1:length(combined.analysis.tables)) {
    print(i)
    
    tbl = combined.analysis.tables[[i]]

    tstats[tbl$gene, names(combined.analysis.tables)[[i]]] = tbl$tstat
    logFC[tbl$gene, names(combined.analysis.tables)[[i]]] = tbl$logFC
    logPvals[tbl$gene, names(combined.analysis.tables)[[i]]] = -log10(tbl$adj.P.Val)
    
    DE.sc[tbl$gene, names(combined.analysis.tables)[[i]]] = tbl$tstat
    
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
limma_trend_mean.scores[is.na(limma_trend_mean.scores)] = 0


Up.genes = lapply(combined.analysis.tables, function(combined.analysis.tbl) {
  combined.analysis.tbl$gene[(combined.analysis.tbl$logFC > 0.1) & (combined.analysis.tbl$adj.P.Val < 0.05)]
})
Down.genes = lapply(combined.analysis.tables, function(combined.analysis.tbl) {
  combined.analysis.tbl$gene[(combined.analysis.tbl$logFC < -0.1) & (combined.analysis.tbl$adj.P.Val < 0.05)]
})

DE.new = list(DE.sc = DE.sc, tstats = tstats, logFC = logFC, logPvals = logPvals, Up.genes = Up.genes, Down.genes = Down.genes)
saveRDS(DE.new, "~/PFC_v3/DE_genes_pseudobulk_final_subtypes.rds")

ALS.DEs = read.table("~/als/MAGMA_DEG_input.tsv", sep = "\t")

suppressWarnings(ids <- AnnotationDbi::mapIds(org.Hs.eg.db, keys = ALS.DEs$V2, keytype = "ENSEMBL", column = "SYMBOL", multiVals = "first"))
'select()' returned 1:many mapping between keys and columns
ids[is.na(ids)] = ""
ids = as.character(ids)

ALS.genes = split(ids, ALS.DEs$V1)
X = do.call(cbind, lapply(ALS.genes, function(gs) as.numeric(rownames(variant_gene_scores) %in% gs)))
rownames(X) = rownames(variant_gene_scores)


# gg = apply(variant_gene_scores, 2, function(x) rownames(X)[scale(x) > 1])


EN = assess.geneset.enrichment.from.scores(variant_gene_scores, as(X, "sparseMatrix"))
logPvals = EN$logPvals
colnames(logPvals) = colnames(variant_gene_scores)


  require(ComplexHeatmap)
  pdf("~/als/MAGMA.pdf", height = 48)
Heatmap(logPvals)
  dev.off()
null device 
          1 
  require(ComplexHeatmap)
  pdf("~/als/MAGMA.pdf", height = 48)
  ComplexHeatmap::Heatmap(Res[, -5], rect_gp = gpar(col = "black"))
Warning in for (i in c(setdiff(seq_len(nc), c(1, nc)), c(1, nc))) { :
  closing unused connection 8 (~/als/magma/hmagmaAdultBrain__sz3_DE.gsa.out)
Warning in for (i in c(setdiff(seq_len(nc), c(1, nc)), c(1, nc))) { :
  closing unused connection 7 (~/als/magma/hmagmaAdultBrain__ms_DE.gsa.out)
Warning in for (i in c(setdiff(seq_len(nc), c(1, nc)), c(1, nc))) { :
  closing unused connection 6 (~/als/magma/hmagmaAdultBrain__alzKunkleNoapoe_DE.gsa.out)
Warning in for (i in c(setdiff(seq_len(nc), c(1, nc)), c(1, nc))) { :
  closing unused connection 4 (~/als/magma/hmagmaAdultBrain__alz2noapoe_DE.gsa.out)
Warning in for (i in c(setdiff(seq_len(nc), c(1, nc)), c(1, nc))) { :
  closing unused connection 3 (~/als/magma/hmagmaAdultBrain__als_DE.gsa.out)
  dev.off()
null device 
          1 

visualize.markers(In.ace, c("CALB1", "CALB2", "CUX2", "NOS1", "NPY"))
[1] "Markers Visualized: CALB1, CALB2, CUX2, NOS1, NPY"
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

# Rosehip
# VIP
# REELIN
# 
# PV-Basker
# PV-Chan
# SST

epi_markers$Sst_Calb1
[1] "HPGD+"   "CALB1+"  "FBN2+"   "TAC3-"   "TH-"     "GXYLT2-" "ADGRG6-"
epi_annot = readr::read_rds("~/PFC_v3/epilepsy_paper_cell_annot.rds")
epi_markers = readr::read_rds("~/PFC_v3/epilepsy_paper_markers.rds")

In.cell.annot = annotate.cells.using.markers(In.ace, epi_markers[20:48])
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
Max it = 5
cc = grep("Sst", colnames(In.cell.annot$Enrichment))
sapply(cc, function(i) {
  plot(plot.ACTIONet.gradient(In.ace, x = In.cell.annot$Enrichment[, i], alpha_val = 0)+ggtitle(colnames(In.cell.annot$Enrichment)[[i]]))
})
            [,1]    [,2]    [,3]    [,4]    [,5]    [,6]    [,7]    [,8]    [,9]    [,10]  
data        List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0 
layers      List,1  List,1  List,1  List,1  List,1  List,1  List,1  List,1  List,1  List,1 
scales      ?       ?       ?       ?       ?       ?       ?       ?       ?       ?      
mapping     List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0  List,0 
theme       List,10 List,10 List,10 List,10 List,10 List,10 List,10 List,10 List,10 List,10
coordinates ?       ?       ?       ?       ?       ?       ?       ?       ?       ?      
facet       ?       ?       ?       ?       ?       ?       ?       ?       ?       ?      
plot_env    ?       ?       ?       ?       ?       ?       ?       ?       ?       ?      
labels      List,6  List,6  List,6  List,6  List,6  List,6  List,6  List,6  List,6  List,6 

annotate.profile.using.markers <- function (feature.scores, marker.genes, rand.sample.no = 1000) 
{
    if (is.matrix(marker.genes) | is.sparseMatrix(marker.genes)) {
        marker.genes = apply(marker.genes, 2, function(x) rownames(marker.genes)[x > 
            0])
    }
    specificity.panel = feature.scores
    GS.names = names(marker.genes)
    if (is.null(GS.names)) {
        GS.names = sapply(1:length(GS.names), function(i) sprintf("Celltype %s", 
            i))
    }
    markers.table = do.call(rbind, lapply(names(marker.genes), 
        function(celltype) {
            genes = marker.genes[[celltype]]
            if (length(genes) == 0) {
                err = sprintf("No markers left.\n")
                stop(err, call. = FALSE)
            }
            signed.count = sum(sapply(genes, function(gene) grepl("\\+$|-$", 
                gene)))
            is.signed = signed.count > 0
            if (!is.signed) {
                df = data.frame(Gene = genes, Direction = +1, 
                  Celltype = celltype, stringsAsFactors = FALSE)
            }
            else {
                pos.genes = (as.character(sapply(genes[grepl("+", 
                  genes, fixed = TRUE)], function(gene) stringr::str_replace(gene, 
                  stringr::fixed("+"), ""))))
                neg.genes = (as.character(sapply(genes[grepl("-", 
                  genes, fixed = TRUE)], function(gene) stringr::str_replace(gene, 
                  stringr::fixed("-"), ""))))
                df = data.frame(Gene = c(pos.genes, neg.genes), 
                  Direction = c(rep(+1, length(pos.genes)), rep(-1, 
                    length(neg.genes))), Celltype = celltype, 
                  stringsAsFactors = FALSE)
            }
        }))
    markers.table = markers.table[markers.table$Gene %in% rownames(specificity.panel), 
        ]
    if (dim(markers.table)[1] == 0) {
        err = sprintf("No markers left.\n")
        stop(err, call. = FALSE)
    }
    specificity.panel = specificity.panel[markers.table$Gene, ]
    IDX = split(1:dim(markers.table)[1], markers.table$Celltype)
    print("Computing significance scores")
    set.seed(0)
    Z = sapply(IDX, function(idx) {
        markers = (as.character(markers.table$Gene[idx]))
        directions = markers.table$Direction[idx]
        mask = markers %in% colnames(specificity.panel)
        A = as.matrix(specificity.panel[markers[mask], ])
        sgn = as.numeric(directions[mask])
        stat = A %*% sgn
        rand.stats = sapply(1:rand.sample.no, function(i) {
            rand.samples = sample.int(dim(specificity.panel)[2], 
                sum(mask))
            rand.A = as.matrix(specificity.panel[, rand.samples])
            rand.stat = rand.A %*% sgn
        })
        cell.zscores = as.numeric((stat - apply(rand.stats, 1, 
            mean))/apply(rand.stats, 1, sd))
        return(cell.zscores)
    })
    Z[is.na(Z)] = 0
    Labels = colnames(Z)[apply(Z, 1, which.max)]
    Labels.conf = apply(Z, 1, max)
    names(Labels) = rownames(specificity.panel)
    names(Labels.conf) = rownames(specificity.panel)
    rownames(Z) = rownames(specificity.panel)
    out = list(Label = Labels, Confidence = Labels.conf, Enrichment = Z)
    return(out)
}

names(curatedMarkers_human$Brain$PFC$Velmeshev2019$marker.genes)
 [1] "AST"              "Endothelial"      "IN-PV"            "IN-SST"           "IN-SV2C"          "IN-VIP"           "L2/3"            
 [8] "L4"               "L5/6"             "L5/6-CC"          "Microglia"        "Neu-NRGN"         "Oligodendrocytes" "OPC"             

Ast.DE = annotate.profile.using.markers(t(spec), mm)
[1] "Computing significance scores"
Heatmap(Ast.DE$Enrichment)

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgaW5jbHVkZT1GQUxTRX0KcmVxdWlyZShtdXNjYXQpCgpgYGAKCmBgYHtyfQpzY2UgPSByZWFkcjo6cmVhZF9yZHMoIn4vcmVzdWx0cy9zdWJfU1pfQXN0T2xpU1NULnJkcyIpCnNjZSA9IGFzKHNjZSwgIlNpbmdsZUNlbGxFeHBlcmltZW50IikKCmBgYAoKCmBgYHtyfQpzY2UkZ3JvdXBfaWQgPSBzY2UkUGhlbm90eXBlCnNjZSRjbHVzdGVyX2lkID0gZmFjdG9yKHNjZSRhc3NpZ25lZF9hcmNoZXR5cGUpCnNjZSRzYW1wbGVfaWQgPSBzY2UkSW5kaXZpZHVhbAoKbGlicmFyeShtdXNjYXQpCnNjZSRpZCA8LSBwYXN0ZTAoc2NlJFBoZW5vdHlwZSwgc2NlJHNhbXBsZV9pZCkKKHNjZSA8LSBwcmVwU0NFKHNjZSwKICAgIGtpZCA9ICJjbHVzdGVyX2lkIiwgIyBzdWJwb3B1bGF0aW9uIGFzc2lnbm1lbnRzCiAgICBnaWQgPSAiZ3JvdXBfaWQiLCAgIyBncm91cCBJRHMgKGN0cmwvc3RpbSkKICAgIHNpZCA9ICJzYW1wbGVfaWQiLCAgICMgc2FtcGxlIElEcyAoY3RybC9zdGltLjEyMzQpCiAgICBkcm9wID0gVFJVRSkpICAjIGRyb3AgYWxsIG90aGVyIGNvbERhdGEgY29sdW1ucwoKcGIubG9nY291bnRzIDwtIGFnZ3JlZ2F0ZURhdGEoc2NlLAogICAgYXNzYXkgPSAibG9nY291bnRzIiwgZnVuID0gIm1lYW4iLAogICAgYnkgPSBjKCJjbHVzdGVyX2lkIiwgInNhbXBsZV9pZCIpKQoKc2F2ZVJEUyhwYi5sb2djb3VudHMsIGZpbGUgPSAifi9QRkNfdjMvUEJfbWVhbl9sb2djb3VudHNfcmVub3JtYWxpemVkX3N1YnR5cGVzLlJEUyIpCgpgYGAKCmBgYHtyfQpwYi5sb2djb3VudHMgPSByZWFkUkRTKGZpbGUgPSAifi9QRkNfdjMvUEJfbWVhbl9sb2djb3VudHNfcmVub3JtYWxpemVkX3N1YnR5cGVzLlJEUyIpCgptZXRhLmRhdGEgPSByZWFkUkRTKCJ+L1BGQ192My9tZXJnZWRfbWV0YWRhdGFfd2l0aF9TWlRSLnJkcyIpCgpjb21tb24uaWRzID0gaW50ZXJzZWN0KGNvbG5hbWVzKHBiLmxvZ2NvdW50cyksIG1ldGEuZGF0YSRJbnRlcm5hbF9JRCkKcGIubG9nY291bnRzID0gcGIubG9nY291bnRzWywgY29tbW9uLmlkc10KbWV0YWRhdGEocGIubG9nY291bnRzKSRuX2NlbGxzID0gbWV0YWRhdGEocGIubG9nY291bnRzKSRuX2NlbGxzWywgY29tbW9uLmlkc10KCmNvbERhdGEocGIubG9nY291bnRzKSA9IGNiaW5kKGNvbERhdGEocGIubG9nY291bnRzKSwgbWV0YS5kYXRhKQoKYGBgCgojIEZpbHRlciBzYW1wbGVzCmBgYHtyfQpuY2VsbHMgPSBtZXRhZGF0YShwYi5sb2djb3VudHMpJG5fY2VsbHMKCm1hc2sgPSAoZmFzdF9jb2x1bW5fc3VtcyhuY2VsbHMpID4gMTAwKQpwYi5sb2djb3VudHMuZmlsdGVyZWQgPSBwYi5sb2djb3VudHNbLCBtYXNrXQptZXRhZGF0YShwYi5sb2djb3VudHMuZmlsdGVyZWQpJG5fY2VsbHMgPSBtZXRhZGF0YShwYi5sb2djb3VudHMuZmlsdGVyZWQpJG5fY2VsbHNbLCBtYXNrXQoKbWV0YS5maWx0ZXJlZCA9IG1ldGEuZGF0YVttYXRjaChjb2xuYW1lcyhwYi5sb2djb3VudHMuZmlsdGVyZWQpLCBtZXRhLmRhdGEkSUQpLCBdCmNvbERhdGEocGIubG9nY291bnRzLmZpbHRlcmVkKSA9IGNiaW5kKGNvbERhdGEocGIubG9nY291bnRzLmZpbHRlcmVkKSwgRGF0YUZyYW1lKG1ldGEuZmlsdGVyZWQpKQoKYGBgCgoKYGBge3J9CiMgTXVzY2F0IGFuYWx5c2lzOiB0d28gY29ob3J0cwpyZXF1aXJlKG11c2NhdCkKcmVxdWlyZShlZGdlUikKcmVxdWlyZShsaW1tYSkKCmZvcm0gPSB+IFBoZW5vdHlwZSArIEJhdGNoICsgR2VuZGVyICsgQWdlICsgUE1JICsgQmVuem9kaWF6ZXBpbmVzICsgQW50aWNvbnZ1bHNhbnRzICsgQW50aXBzeWNoVHlwICsgQW50aXBzeWNoQXR5cCArIEFudGlkZXByZXNzCgpyZXNERSA9IGxhcHBseSggbGV2ZWxzKHBiLmxvZ2NvdW50cy5maWx0ZXJlZCRDb2hvcnQpLCBmdW5jdGlvbihjaHJ0KXsKCglrZWVwLmlkcyA9IGNvbG5hbWVzKHBiLmxvZ2NvdW50cy5maWx0ZXJlZClbcGIubG9nY291bnRzLmZpbHRlcmVkJENvaG9ydCA9PSBjaHJ0XQoKCXBiLmxvZ2NvdW50cy5maWx0ZXJlZF9zdWIgPSBwYi5sb2djb3VudHMuZmlsdGVyZWRbLGtlZXAuaWRzXQoJbWV0YWRhdGEocGIubG9nY291bnRzLmZpbHRlcmVkX3N1Yikkbl9jZWxscyA9IG1ldGFkYXRhKHBiLmxvZ2NvdW50cy5maWx0ZXJlZF9zdWIpJG5fY2VsbHNbLGtlZXAuaWRzXQoKCWRlc2lnbi5tYXQgPC0gbW9kZWwubWF0cml4KGZvcm0sIGRhdGEgPSBkcm9wbGV2ZWxzKGNvbERhdGEocGIubG9nY291bnRzLmZpbHRlcmVkX3N1YikpKQoKCWNvbG5hbWVzKGRlc2lnbi5tYXQpWzFdID0gYygiSW50ZXJjZXB0IikKCgljb250cmFzdC5tYXQgPC0gbWFrZUNvbnRyYXN0cyhjb250cmFzdHMgPSAiUGhlbm90eXBlU1oiLCBsZXZlbHMgPSBkZXNpZ24ubWF0KQoKCXBiRFMocGIubG9nY291bnRzLmZpbHRlcmVkX3N1YiwgbWV0aG9kID0gImxpbW1hLXRyZW5kIiwgbWluX2NlbGxzID0gNSwgZGVzaWduID0gZGVzaWduLm1hdCwgY29udHJhc3QgPSAgY29udHJhc3QubWF0LCBmaWx0ZXIgPSAiZ2VuZSIpCn0pCm5hbWVzKHJlc0RFKSA9IGxldmVscyhjb2xEYXRhKHBiLmxvZ2NvdW50cy5maWx0ZXJlZCkkQ29ob3J0KQoKcmVhZHI6OndyaXRlX3JkcyhyZXNERSwgIn4vUEZDX3YzL2ZpbHRlcmVkX3Jlc0RFX3N1YnR5cGVzLnJkcyIpCgoKYGBgCgpgYGB7cn0KY2VsbHR5cGVzID0gaW50ZXJzZWN0KG5hbWVzKHJlc0RFJE1jTGVhbiR0YWJsZSRQaGVub3R5cGVTWiksIG5hbWVzKHJlc0RFJE10U2luYWkkdGFibGUkUGhlbm90eXBlU1opKQoKZmlsdGVyZWQudGFibGVzID0gbGFwcGx5KGNlbGx0eXBlcywgZnVuY3Rpb24oY2VsbHR5cGUpIHsKICB0YmwxID0gcmVzREVbWzFdXSR0YWJsZSRQaGVub3R5cGVTWltbY2VsbHR5cGVdXQogIHRibDIgPSByZXNERVtbMl1dJHRhYmxlJFBoZW5vdHlwZVNaW1tjZWxsdHlwZV1dCiAgCiAgZ2VuZXMgPSBpbnRlcnNlY3QodGJsMSRnZW5lWzEgPD0gYWJzKHRibDEkbG9nRkMvc2QodGJsMSRsb2dGQykpXSwgdGJsMiRnZW5lWzEgPD0gYWJzKHRibDIkbG9nRkMgLyBzZCh0YmwyJGxvZ0ZDKSldKQogIAogIHRibDEgPSB0YmwxW21hdGNoKGdlbmVzLCB0YmwxJGdlbmUpLCBdCiAgdGJsMiA9IHRibDJbbWF0Y2goZ2VuZXMsIHRibDIkZ2VuZSksIF0KICAKICBtYXNrID0gc2lnbih0YmwxJGxvZ0ZDKSpzaWduKHRibDIkbG9nRkMpID4gMAogIHRibDEgPSB0YmwxW21hc2ssIF0KICB0YmwyID0gdGJsMlttYXNrLCBdCiAgCiAgdGJscyA9IGxpc3QoTWNDbGVhbiA9IHRibDEsIE10U2luYWkgPSB0YmwyKSAKICB0YmxzID0gbGFwcGx5KCB0YmxzLCBmdW5jdGlvbih0YWIpewogICAgdGFiJHNlID0gdGFiJGxvZ0ZDIC8gdGFiJHQKICAgIHRhYgogIH0pCiAgCiAgcmV0dXJuKHRibHMpCn0pCm5hbWVzKGZpbHRlcmVkLnRhYmxlcykgPSBjZWxsdHlwZXMKCnJlYWRyOjp3cml0ZV9yZHMoZmlsdGVyZWQudGFibGVzLCAifi9QRkNfdjMvaW5kaXZpZHVhbF9kaWZmX3Jlc3VsdHNfZmlsdGVyZWRfc3VidHlwZXMucmRzIikKCmBgYAoKCgojIyBFeHBvcnQgYXMgZXhjZWwgdGFibGVzCmBgYHtyfQpmb3IoZHMgaW4gMToyKSB7CgogIGxpYnJhcnkob3Blbnhsc3gpCiAgVXAud2IgPC0gY3JlYXRlV29ya2Jvb2soKQogIGZvcihpIGluIDE6bGVuZ3RoKHRibHMpKSB7CiAgICByZXMgPSBmaWx0ZXJlZC50YWJsZXNbW2ldXVtbZHNdXQogICAgcmVzID0gcmVzW3JlcyRsb2dGQyA+IDAsIF0KICAgIHJlcyA9IGNiaW5kKGRhdGEuZnJhbWUoR2VuZSA9IHJvd25hbWVzKHJlcykpLCByZXMpCiAgICByZXMgPSByZXNbb3JkZXIocmVzJHQsIGRlY3JlYXNpbmcgPSBUKSwgXQogIAogICAgbiA9IG5hbWVzKGZpbHRlcmVkLnRhYmxlcylbW2ldXSAjc3RyX3JlcGxhY2UoYXJjaC5uYW1lc1thcmNoLm9yZGVyW2ldXSwgIi8iLCAiLSIpCiAgICAKICAgIGFkZFdvcmtzaGVldCh3Yj1VcC53Yiwgc2hlZXROYW1lID0gbikKICAgIHdyaXRlRGF0YShVcC53Yiwgc2hlZXQgPSBuLCByZXMpIAogIAogIH0KICBzYXZlV29ya2Jvb2soVXAud2IsIHNwcmludGYoIn4vUEZDX3YzL0RFX2dlbmVzX3VwXyVzX3N1YnR5cGUueGxzeCIsIG5hbWVzKGZpbHRlcmVkLnRhYmxlc1tbaV1dKVtbZHNdXSksIG92ZXJ3cml0ZSA9IFRSVUUpCiAgCiAgbGlicmFyeShvcGVueGxzeCkKICBEb3duLndiIDwtIGNyZWF0ZVdvcmtib29rKCkKICBmb3IoaSBpbiAxOmxlbmd0aCh0YmxzKSkgewogICAgcmVzID0gZmlsdGVyZWQudGFibGVzW1tpXV1bW2RzXV0KICAgIHJlcyA9IHJlc1tyZXMkbG9nRkMgPCAwLCBdCiAgICByZXMgPSBjYmluZChkYXRhLmZyYW1lKEdlbmUgPSByb3duYW1lcyhyZXMpKSwgcmVzKQogICAgcmVzID0gcmVzW29yZGVyKHJlcyR0LCBkZWNyZWFzaW5nID0gRiksIF0KICAgIAogICAgbiA9IG5hbWVzKGZpbHRlcmVkLnRhYmxlcylbW2ldXSAjc3RyX3JlcGxhY2UoYXJjaC5uYW1lc1thcmNoLm9yZGVyW2ldXSwgIi8iLCAiLSIpCiAgICAKICAgIGFkZFdvcmtzaGVldCh3Yj1Eb3duLndiLCBzaGVldE5hbWUgPSBuKQogICAgd3JpdGVEYXRhKERvd24ud2IsIHNoZWV0ID0gbiwgcmVzKSAKICAKICB9CiAgc2F2ZVdvcmtib29rKERvd24ud2IsIHNwcmludGYoIn4vUEZDX3YzL0RFX2dlbmVzX2Rvd25fJXNfc3VidHlwZS54bHN4IiwgbmFtZXMoZmlsdGVyZWQudGFibGVzW1tpXV0pW1tkc11dKSwgb3ZlcndyaXRlID0gVFJVRSkKfQoKCgpgYGAKCgpgYGB7cn0KY29tYmluZWQuYW5hbHlzaXMudGFibGVzID0gbGFwcGx5KG5hbWVzKGZpbHRlcmVkLnRhYmxlcyksIGZ1bmN0aW9uKGNlbGx0eXBlKSB7CiAgcHJpbnQoY2VsbHR5cGUpCiAgdGJscyA9IGZpbHRlcmVkLnRhYmxlc1tbY2VsbHR5cGVdXQogIAogIGdlbmUudGJscyA9IGxhcHBseSgxOm5yb3codGJsc1tbMV1dKSwgZnVuY3Rpb24oaSkgewogICAgZGZzID0gbGFwcGx5KDE6bGVuZ3RoKHRibHMpLCBmdW5jdGlvbihrKSB0YmxzW1trXV1baSwgXSkKICAgIGRmID0gZG8uY2FsbCgicmJpbmQiLCBkZnMpCiAgfSkKICBuYW1lcyhnZW5lLnRibHMpID0gdGJsc1tbMV1dJGdlbmUKICAgIAogIGNvbWJpbmVkLmFuYWx5c2lzLnRibCA9IGRvLmNhbGwocmJpbmQsIGxhcHBseShuYW1lcyhnZW5lLnRibHMpLCBmdW5jdGlvbihnZW5lKXsKICAgIHggPSBzdXBwcmVzc1dhcm5pbmdzKG1ldGFmb3I6OnJtYSh5aT1sb2dGQywgc2VpPXNlLCBkYXRhID0gZ2VuZS50YmxzW1tnZW5lXV0sIG1ldGhvZD0iRkUiKSkKICAgIGNvbWJpbmVkLnRibCA9IGRhdGEuZnJhbWUoIGdlbmUgPSBnZW5lLCAKICAgICAgICBsb2dGQyAgICAgPSB4JGJldGEsCiAgICAgICAgc2UgICAgICAgID0geCRzZSwKICAgICAgICB0c3RhdCA9IHgkenZhbCwKICAgICAgICBQLlZhbHVlICAgPSB4JHB2YWwpCiAgICByZXR1cm4oY29tYmluZWQudGJsKQogIH0pKQogIHJvd25hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRibCkgPSBuYW1lcyhnZW5lLnRibHMpCiAgCiAgY29tYmluZWQuYW5hbHlzaXMudGJsID0gY29tYmluZWQuYW5hbHlzaXMudGJsW29yZGVyKGNvbWJpbmVkLmFuYWx5c2lzLnRibCRQLlZhbHVlKSwgXQogIAogIHJldHVybihjb21iaW5lZC5hbmFseXNpcy50YmwpCn0pCm5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykgPSBuYW1lcyhmaWx0ZXJlZC50YWJsZXMpCgpERiA9IGRvLmNhbGwocmJpbmQsIGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykKREYkYWRqLlAuVmFsID0gcC5hZGp1c3QoREYkUC5WYWx1ZSwgImZkciIpCmNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcyA9IHNwbGl0KERGLCB1bmxpc3QobGFwcGx5KG5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcyksIGZ1bmN0aW9uKGNlbGx0eXBlKSByZXAoY2VsbHR5cGUsIG5yb3coY29tYmluZWQuYW5hbHlzaXMudGFibGVzW1tjZWxsdHlwZV1dKSkpKSkKCnJlYWRyOjp3cml0ZV9yZHMoY29tYmluZWQuYW5hbHlzaXMudGFibGVzLCAifi9QRkNfdjMvbWV0YV9hbmFseXNpc19kaWZmX3Jlc3VsdHNfc3VidHlwZXMucmRzIikKCmBgYAoKYGBge3J9CnJlc0RFID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9maWx0ZXJlZF9yZXNERS5yZHMiKQoKZmlsdGVyZWQudGFibGVzID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9pbmRpdmlkdWFsX2RpZmZfcmVzdWx0c19maWx0ZXJlZC5yZHMiKQoKY29tYmluZWQuYW5hbHlzaXMudGFibGVzID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9tZXRhX2FuYWx5c2lzX2RpZmZfcmVzdWx0cy5yZHMiKQoKYGBgCgpgYGB7cn0KICBsaWJyYXJ5KG9wZW54bHN4KQogIFVwLndiIDwtIGNyZWF0ZVdvcmtib29rKCkKICBmb3IoaSBpbiAxOmxlbmd0aChjb21iaW5lZC5hbmFseXNpcy50YWJsZXMpKSB7CiAgICByZXMgPSBjb21iaW5lZC5hbmFseXNpcy50YWJsZXNbW2ldXQogICAgcmVzID0gcmVzWyhyZXMkbG9nRkMgPiAwLjEpICYgKHJlcyRQLlZhbHVlIDw9IDAuMDUpLCBdCiAgICByZXMgPSByZXNbb3JkZXIocmVzJHQsIGRlY3JlYXNpbmcgPSBUKSwgXQogIAogICAgbiA9IG5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcylbW2ldXSAjc3RyX3JlcGxhY2UoYXJjaC5uYW1lc1thcmNoLm9yZGVyW2ldXSwgIi8iLCAiLSIpCiAgICAKICAgIGFkZFdvcmtzaGVldCh3Yj1VcC53Yiwgc2hlZXROYW1lID0gbikKICAgIHdyaXRlRGF0YShVcC53Yiwgc2hlZXQgPSBuLCByZXMpIAogIAogIH0KICBzYXZlV29ya2Jvb2soVXAud2IsIHNwcmludGYoIn4vUEZDX3YzL0RFX2dlbmVzX3VwXyVzX3N1YnR5cGUueGxzeCIsICJjb21iaW5lZCIpLCBvdmVyd3JpdGUgPSBUUlVFKQogIAogIAogIGxpYnJhcnkob3Blbnhsc3gpCiAgRG93bi53YiA8LSBjcmVhdGVXb3JrYm9vaygpCiAgZm9yKGkgaW4gMTpsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkgewogICAgcmVzID0gY29tYmluZWQuYW5hbHlzaXMudGFibGVzW1tpXV0KICAgIHJlcyA9IHJlc1socmVzJGxvZ0ZDIDwgLTAuMSkgJiAocmVzJFAuVmFsdWUgPD0gMC4wNSksIF0KICAgIHJlcyA9IHJlc1tvcmRlcihyZXMkdCwgZGVjcmVhc2luZyA9IEYpLCBdCiAgICAKICAgIG4gPSBuYW1lcyhjb21iaW5lZC5hbmFseXNpcy50YWJsZXMpW1tpXV0gI3N0cl9yZXBsYWNlKGFyY2gubmFtZXNbYXJjaC5vcmRlcltpXV0sICIvIiwgIi0iKQogICAgCiAgICBhZGRXb3Jrc2hlZXQod2I9RG93bi53Yiwgc2hlZXROYW1lID0gbikKICAgIHdyaXRlRGF0YShEb3duLndiLCBzaGVldCA9IG4sIHJlcykgCiAgCiAgfQogIHNhdmVXb3JrYm9vayhEb3duLndiLCBzcHJpbnRmKCJ+L1BGQ192My9ERV9nZW5lc19kb3duXyVzX3N1YnR5cGUueGxzeCIsICJjb21iaW5lZCIpLCBvdmVyd3JpdGUgPSBUUlVFKQpgYGAKCgoKCmBgYHtyfQpERS5zYyA9IG1hdHJpeCgwLCBucm93KHBiLmxvZ2NvdW50cyksIGxlbmd0aChjb21iaW5lZC5hbmFseXNpcy50YWJsZXMpKQp0c3RhdHMgPSBtYXRyaXgoMCwgbnJvdyhwYi5sb2djb3VudHMpLCBsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkKbG9nRkMgPSBtYXRyaXgoMCwgbnJvdyhwYi5sb2djb3VudHMpLCBsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkKbG9nUHZhbHMgPSBtYXRyaXgoMCwgbnJvdyhwYi5sb2djb3VudHMpLCBsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkKcm93bmFtZXMoREUuc2MpID0gcm93bmFtZXModHN0YXRzKSA9IHJvd25hbWVzKGxvZ0ZDKSA9IHJvd25hbWVzKGxvZ1B2YWxzKSA9IHJvd25hbWVzKHBiLmxvZ2NvdW50cykKY29sbmFtZXMoREUuc2MpID0gY29sbmFtZXModHN0YXRzKSA9IGNvbG5hbWVzKGxvZ0ZDKSA9IGNvbG5hbWVzKGxvZ1B2YWxzKSA9IG5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykKCmxpbW1hX3RyZW5kX21lYW4uc2NvcmVzID0gbWF0cml4KDAsIG5yb3cocGIubG9nY291bnRzKSwgbGVuZ3RoKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykpClVwLmdlbmVzID0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykpCkRvd24uZ2VuZXMgPSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkKcm93bmFtZXMobGltbWFfdHJlbmRfbWVhbi5zY29yZXMpID0gcm93bmFtZXMocGIubG9nY291bnRzKQpuYW1lcyhVcC5nZW5lcykgPSBuYW1lcyhEb3duLmdlbmVzKSA9IGNvbG5hbWVzKGxpbW1hX3RyZW5kX21lYW4uc2NvcmVzKSA9IG5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcykKZm9yKGkgaW4gMTpsZW5ndGgoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKSkgewoJcHJpbnQoaSkKCQoJdGJsID0gY29tYmluZWQuYW5hbHlzaXMudGFibGVzW1tpXV0KCgl0c3RhdHNbdGJsJGdlbmUsIG5hbWVzKGNvbWJpbmVkLmFuYWx5c2lzLnRhYmxlcylbW2ldXV0gPSB0YmwkdHN0YXQKCWxvZ0ZDW3RibCRnZW5lLCBuYW1lcyhjb21iaW5lZC5hbmFseXNpcy50YWJsZXMpW1tpXV1dID0gdGJsJGxvZ0ZDCglsb2dQdmFsc1t0YmwkZ2VuZSwgbmFtZXMoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKVtbaV1dXSA9IC1sb2cxMCh0YmwkYWRqLlAuVmFsKQoJCglERS5zY1t0YmwkZ2VuZSwgbmFtZXMoY29tYmluZWQuYW5hbHlzaXMudGFibGVzKVtbaV1dXSA9IHRibCR0c3RhdAoJCn0KbGltbWFfdHJlbmRfbWVhbi5zY29yZXNbaXMubmEobGltbWFfdHJlbmRfbWVhbi5zY29yZXMpXSA9IDAKCgpVcC5nZW5lcyA9IGxhcHBseShjb21iaW5lZC5hbmFseXNpcy50YWJsZXMsIGZ1bmN0aW9uKGNvbWJpbmVkLmFuYWx5c2lzLnRibCkgewogIGNvbWJpbmVkLmFuYWx5c2lzLnRibCRnZW5lWyhjb21iaW5lZC5hbmFseXNpcy50YmwkbG9nRkMgPiAwLjEpICYgKGNvbWJpbmVkLmFuYWx5c2lzLnRibCRhZGouUC5WYWwgPCAwLjA1KV0KfSkKRG93bi5nZW5lcyA9IGxhcHBseShjb21iaW5lZC5hbmFseXNpcy50YWJsZXMsIGZ1bmN0aW9uKGNvbWJpbmVkLmFuYWx5c2lzLnRibCkgewogIGNvbWJpbmVkLmFuYWx5c2lzLnRibCRnZW5lWyhjb21iaW5lZC5hbmFseXNpcy50YmwkbG9nRkMgPCAtMC4xKSAmIChjb21iaW5lZC5hbmFseXNpcy50YmwkYWRqLlAuVmFsIDwgMC4wNSldCn0pCgpERS5uZXcgPSBsaXN0KERFLnNjID0gREUuc2MsIHRzdGF0cyA9IHRzdGF0cywgbG9nRkMgPSBsb2dGQywgbG9nUHZhbHMgPSBsb2dQdmFscywgVXAuZ2VuZXMgPSBVcC5nZW5lcywgRG93bi5nZW5lcyA9IERvd24uZ2VuZXMpCnNhdmVSRFMoREUubmV3LCAifi9QRkNfdjMvREVfZ2VuZXNfcHNldWRvYnVsa19maW5hbF9zdWJ0eXBlcy5yZHMiKQoKCmBgYAoKYGBge3J9CkRFLm5ldy5mdWxsID0gcmVhZFJEUygifi9QRkNfdjMvREVfZ2VuZXNfcHNldWRvYnVsa19maW5hbC5yZHMiKQoKCkNDID0gY29yKERFLm5ldy5mdWxsJERFLnNjLCBERS5uZXckREUuc2MpCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKENDKQoKYGBgCmBgYHtyfQpBTFMuREVzID0gcmVhZC50YWJsZSgifi9hbHMvTUFHTUFfREVHX2lucHV0LnRzdiIsIHNlcCA9ICJcdCIpCgpzdXBwcmVzc1dhcm5pbmdzKGlkcyA8LSBBbm5vdGF0aW9uRGJpOjptYXBJZHMob3JnLkhzLmVnLmRiLCBrZXlzID0gQUxTLkRFcyRWMiwga2V5dHlwZSA9ICJFTlNFTUJMIiwgY29sdW1uID0gIlNZTUJPTCIsIG11bHRpVmFscyA9ICJmaXJzdCIpKQppZHNbaXMubmEoaWRzKV0gPSAiIgppZHMgPSBhcy5jaGFyYWN0ZXIoaWRzKQoKQUxTLmdlbmVzID0gc3BsaXQoaWRzLCBBTFMuREVzJFYxKQpYID0gZG8uY2FsbChjYmluZCwgbGFwcGx5KEFMUy5nZW5lcywgZnVuY3Rpb24oZ3MpIGFzLm51bWVyaWMocm93bmFtZXModmFyaWFudF9nZW5lX3Njb3JlcykgJWluJSBncykpKQpyb3duYW1lcyhYKSA9IHJvd25hbWVzKHZhcmlhbnRfZ2VuZV9zY29yZXMpCgoKIyBnZyA9IGFwcGx5KHZhcmlhbnRfZ2VuZV9zY29yZXMsIDIsIGZ1bmN0aW9uKHgpIHJvd25hbWVzKFgpW3NjYWxlKHgpID4gMV0pCgoKRU4gPSBhc3Nlc3MuZ2VuZXNldC5lbnJpY2htZW50LmZyb20uc2NvcmVzKHZhcmlhbnRfZ2VuZV9zY29yZXMsIGFzKFgsICJzcGFyc2VNYXRyaXgiKSkKbG9nUHZhbHMgPSBFTiRsb2dQdmFscwpjb2xuYW1lcyhsb2dQdmFscykgPSBjb2xuYW1lcyh2YXJpYW50X2dlbmVfc2NvcmVzKQoKCiAgcmVxdWlyZShDb21wbGV4SGVhdG1hcCkKICBwZGYoIn4vYWxzL01BR01BLnBkZiIsIGhlaWdodCA9IDQ4KQpIZWF0bWFwKGxvZ1B2YWxzKQogIGRldi5vZmYoKQoKYGBgCgpgYGB7cn0KZGwgPSBsaXN0LmRpcnMoZmlsZS5wYXRoKE1BR01BLnBhdGgsICJobWFnbWEiKSwgZnVsbC5uYW1lcyA9IEYsIHJlY3Vyc2l2ZSA9IEYpCgoKICBwdmFscyA9ICAgCiAgbGFwcGx5KGRsLCBmdW5jdGlvbihjb25kKSB7CiAgICBwcmludChjb25kKQogICAgZmlsZS5uYW1lID0gc3ByaW50ZigiJXMvJXNfJXMuZ3NhLm91dCIsIE1BR01BLnBhdGgsIGNvbmQsIGQpCiAgICBsaW5lcyA9IHJlYWRMaW5lcyhjb24gPC0gZmlsZShmaWxlLm5hbWUpKQogICAgbGluZXMgPSBzdHJfc3BsaXQobGluZXMsICJcbiIpWy1jKDE6NSldCiAgICAKICAgIHB2YWxzID0gc2FwcGx5KGxpbmVzLCBmdW5jdGlvbihsbCkgewogICAgICBwYXJ0cyA9IHN0cl9zcGxpdChsbCwgIiAiKQogICAgICBhcy5udW1lcmljKHBhcnRzW1sxXV1bbGVuZ3RoKHBhcnRzW1sxXV0pLTFdKQogICAgfSkKICAgIAogICAgbmFtZXMocHZhbHMpID0gc2FwcGx5KGxpbmVzLCBmdW5jdGlvbihsbCkgewogICAgICBwYXJ0cyA9IHN0cl9zcGxpdChsbCwgIiAiKQogICAgICBwYXJ0c1tbMV1dW2xlbmd0aChwYXJ0c1tbMV1dKV0KICAgIH0pCiAgICAKICAgIHJldHVybihwdmFscykKICB9KQogIG5hbWVzKHB2YWxzKSA9IGRsCiAgCiAgeHggPSBzb3J0KHVuaXF1ZShSZWR1Y2UoImludGVyc2VjdCIsIHNhcHBseShwdmFscywgbmFtZXMpKSkpCgogIFJlcyA9IC1sb2cxMChkby5jYWxsKGNiaW5kLCBsYXBwbHkocHZhbHMsIGZ1bmN0aW9uKHgpIHhbeHhdKSkpCgogIHJlcXVpcmUoQ29tcGxleEhlYXRtYXApCiAgcGRmKCJ+L2Fscy9NQUdNQS5wZGYiLCBoZWlnaHQgPSA0OCkKICBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChSZXNbLCAtNV0sIHJlY3RfZ3AgPSBncGFyKGNvbCA9ICJibGFjayIpKQogIGRldi5vZmYoKQogIApgYGAKCgpgYGB7cn0KSW4uYWNlID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9Jbl9hbm5vdGF0ZWRfc3ViQUNUSU9OZXQucmRzIikKCnBsb3QuQUNUSU9OZXQoSW4uYWNlLCBJbi5hY2UkTGFiZWxzLmZpbmFsKQoKYGBgCgpgYGB7cn0KdmlzdWFsaXplLm1hcmtlcnMoSW4uYWNlLCBjKCJDQUxCMSIsICJDQUxCMiIsICJDVVgyIiwgIk5PUzEiLCAiTlBZIikpCgpgYGAKCgpgYGB7cn0KIyBSb3NlaGlwCiMgVklQCiMgUkVFTElOCiMgCiMgUFYtQmFza2VyCiMgUFYtQ2hhbgojIFNTVAoKYGBgCgoKYGBge3J9CmNlbGwuYW5ub3RzID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9TWl9jZWxsX21ldGEucmRzIikKaWR4ID0gbWF0Y2goY29sbmFtZXMoSW4uYWNlKSwgcm93bmFtZXMoY2VsbC5hbm5vdHMpKQoKYXJjaHMgPSBjZWxsLmFubm90cyRhc3NpZ25lZF9hcmNoZXR5cGVbaWR4XQoKCnBsb3QuQUNUSU9OZXQoSW4uYWNlLCBJbi5hY2UkTGFiZWxzLmZpbmFsKQpwbG90LkFDVElPTmV0KEluLmFjZSwgYXJjaHMpCgoKSW4uYWNlID0gcmVydW4uYXJjaGV0eXBlLmFnZ3JlZ2F0aW9uKEluLmFjZSkKCmNsID0gQUNUSU9OZXQ6OkxlaWRlbi5jbHVzdGVyaW5nKEluLmFjZSwgMC4yNSkKCnBsb3QuQUNUSU9OZXQoSW4uYWNlLCBJbi5hY2UkYXNzaWduZWRfYXJjaGV0eXBlKQpwbG90LkFDVElPTmV0KEluLmFjZSwgY2wpCgoKCgpgYGAKYGBge3J9CkluLm1hcmtlcnMuc2ViYXN0aWFuID0gbGlzdChJbi5TU1RfQURBTVRTMTkgPSBjKCJHUklLMSIsICJLSUYyNkIiLCAiU1lOUFIiLCAiWEtSNCIsICJLSUFBMTIxNyIsICJHUklQMSIsICJUSFNEN0EiLCAiQ09MMjVBMSIsICJST0JPMiIsICJSQk1TMyIsICJURU5NMyIsICJaTkYzODVEIiwgIlRFTk0xIiwgIkdSSUszIiwgIk5YUEgxIiwgIkJDTDExQSIsICJTT1g2IiwgIk5FVE8yIiwgIlNISVNBNiIsICJEQUIxIiwgIlBJUDVLMUIiLCAiQ0RIMTIiLCAiUENESDExWCIsICJHUklLMiIsICJDREg3IiwgIlNMQzI0QTMiLCAiQURBTVRTMTkiLCAiUEFDUkciLCAiQ0RIOSIpLCBJbi5TU1RfQlJJTlAzID0gYygiVEhTRDdCIiwgIkZCTjIiLCAiVFJIREUiLCAiU0FNRDUiLCAiR1JJTjNBIiwgIlNZTlBSIiwgIkJSSU5QMyIsICJOUEFTMSIsICJTTElUMiIsICJQQVdSIiwgIlBMQ0gxIiwgIlJBQjNDIiwgIkdQQzYiLCAiR1JNMSIsICJQVENIRDQiLCAiTlhQSDEiLCAiS0NOTUIyIiwgIktJQUExMjE3IiwgIkM4b3JmMzQiLCAiWk5GMzg1RCIsICJORFNUNCIsICJYS1I0IiwgIlBUUFJNIiwgIlBBTSIsICJGTFQzIiwgIlBDREgxMVgiLCAiR1JJSzEiLCAiTkVUTzEiLCAiUlVOWDFUMSIpLCBJbi5TU1RfR0FMTlQxNCA9IGMoIlRSSERFIiwgIkdSSUsxIiwgIlNZTlBSIiwgIlNISVNBNiIsICJYS1I0IiwgIkdBTE5UMTQiLCAiUENESDExWCIsICJLSUFBMTIxNyIsICJHUklOM0EiLCAiVEVOTTMiLCAiU0FNRDUiLCAiR1JJUDEiLCAiTkVUTzEiLCAiQ09MMTlBMSIsICJHUklLMyIsICJDT0wyNUExIiwgIkVMQVZMMiIsICJLSUYyNkIiLCAiS0NOTUIyIiwgIlBMQ0gxIiwgIkNESDkiLCAiUk9CTzIiLCAiUEFNIiwgIk5YUEgxIiwgIkFEQ1k4IiwgIlRTSFozIiwgIlJBQjNDIiwgIkdSSUsyIiwgIkdSTTEiKSwgSW4uU1NUX05QWSA9IGMoIk5QWSIsICJOT1MxIiwgIlRBQ1IxIiwgIlNTVCIsICJJTDFSQVBMMiIsICJNUFBFRDIiLCAiR1JJSzEiLCAiTFJQOCIsICJDSFJNMiIsICJDT1JUIiwgIkNSSEJQIiwgIkFGRjIiLCAiU09YNiIsICJUSFNENCIsICJUTVRDMSIsICJOWFBIMiIsICJHVUxQMSIsICJQQU0iLCAiTUFSQ0hGNCIsICJTVklMIiwgIkJDTDExQSIsICJUUlBDNiIsICJBUkhHQVAyOCIsICJCQ0wxMUIiLCAiQ09MMjRBMSIsICJURU5UNUEiLCAiTkhTIiwgIlNDTjlBIiwgIktMRjUiKSkKCmFubm90Lm1lID0gYW5ub3RhdGUuY2VsbHMudXNpbmcubWFya2VycyhJbi5hY2UsIEluLm1hcmtlcnMuc2ViYXN0aWFuKQoKcGxvdC5BQ1RJT05ldC5ncmFkaWVudChJbi5hY2UsIGFubm90Lm1lJEVucmljaG1lbnRbLCAxXSkKcGxvdC5BQ1RJT05ldC5ncmFkaWVudChJbi5hY2UsIGFubm90Lm1lJEVucmljaG1lbnRbLCAyXSkKcGxvdC5BQ1RJT05ldC5ncmFkaWVudChJbi5hY2UsIGFubm90Lm1lJEVucmljaG1lbnRbLCAzXSkKcGxvdC5BQ1RJT05ldC5ncmFkaWVudChJbi5hY2UsIGFubm90Lm1lJEVucmljaG1lbnRbLCA0XSkKCnZpc3VhbGl6ZS5tYXJrZXJzKEluLmFjZSwgYygiSFBHRCIsICJGQk4yIikpCgoKYGBgCgpgYGB7cn0KZXBpX2Fubm90ID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9lcGlsZXBzeV9wYXBlcl9jZWxsX2Fubm90LnJkcyIpCmVwaV9tYXJrZXJzID0gcmVhZHI6OnJlYWRfcmRzKCJ+L1BGQ192My9lcGlsZXBzeV9wYXBlcl9tYXJrZXJzLnJkcyIpCgpJbi5jZWxsLmFubm90ID0gYW5ub3RhdGUuY2VsbHMudXNpbmcubWFya2VycyhJbi5hY2UsIGVwaV9tYXJrZXJzWzIwOjQ4XSkKCmNjID0gZ3JlcCgiU3N0IiwgY29sbmFtZXMoSW4uY2VsbC5hbm5vdCRFbnJpY2htZW50KSkKc2FwcGx5KGNjLCBmdW5jdGlvbihpKSB7CiAgcGxvdChwbG90LkFDVElPTmV0LmdyYWRpZW50KEluLmFjZSwgeCA9IEluLmNlbGwuYW5ub3QkRW5yaWNobWVudFssIGldLCBhbHBoYV92YWwgPSAwKStnZ3RpdGxlKGNvbG5hbWVzKEluLmNlbGwuYW5ub3QkRW5yaWNobWVudClbW2ldXSkpCn0pCgoKcGxvdC5BQ1RJT05ldChJbi5hY2UsIEluLmNlbGwuYW5ub3QkTGFiZWwpCgpgYGAKYGBge3J9CgpgYGAKCgpgYGB7cn0KY2VsbC5hbm5vdHMgPSByZWFkcjo6cmVhZF9yZHMoIn4vUEZDX3YzL1NaX2NlbGxfbWV0YS5yZHMiKQppZHggPSBtYXRjaChjb2xuYW1lcyhJbi5hY2UpLCByb3duYW1lcyhjZWxsLmFubm90cykpCgojIGNvbG5hbWVzKGVwaV9hbm5vdCRFbnJpY2htZW50KQoKY3RzID0gZXBpX2Fubm90JExhYmVsW2lkeF0KCgpwbG90LkFDVElPTmV0KEluLmFjZSwgSW4uYWNlJExhYmVscy5maW5hbCkKcGxvdC5BQ1RJT05ldChJbi5hY2UsIGN0cykKCmBgYApgYGB7cn0Kc3BlYyA9IHJlYWRyOjpyZWFkX3Jkcygifi9QRkNfdjMvU1pfYXJjaF9nZW5lX3NwZWMucmRzIikKU1NULmFyY2hzID0gc3BlY1ssIGMoMTYsIDIxLCAyNSldCgpwbG90LnRvcC5rLmZlYXR1cmVzKFNTVC5hcmNocywgdG9wX2ZlYXR1cmVzID0gMTApCgpgYGAKCmBgYHtyfQphbm5vdGF0ZS5wcm9maWxlLnVzaW5nLm1hcmtlcnMgPC0gZnVuY3Rpb24gKGZlYXR1cmUuc2NvcmVzLCBtYXJrZXIuZ2VuZXMsIHJhbmQuc2FtcGxlLm5vID0gMTAwMCkgCnsKICAgIGlmIChpcy5tYXRyaXgobWFya2VyLmdlbmVzKSB8IGlzLnNwYXJzZU1hdHJpeChtYXJrZXIuZ2VuZXMpKSB7CiAgICAgICAgbWFya2VyLmdlbmVzID0gYXBwbHkobWFya2VyLmdlbmVzLCAyLCBmdW5jdGlvbih4KSByb3duYW1lcyhtYXJrZXIuZ2VuZXMpW3ggPiAKICAgICAgICAgICAgMF0pCiAgICB9CiAgICBzcGVjaWZpY2l0eS5wYW5lbCA9IGZlYXR1cmUuc2NvcmVzCiAgICBHUy5uYW1lcyA9IG5hbWVzKG1hcmtlci5nZW5lcykKICAgIGlmIChpcy5udWxsKEdTLm5hbWVzKSkgewogICAgICAgIEdTLm5hbWVzID0gc2FwcGx5KDE6bGVuZ3RoKEdTLm5hbWVzKSwgZnVuY3Rpb24oaSkgc3ByaW50ZigiQ2VsbHR5cGUgJXMiLCAKICAgICAgICAgICAgaSkpCiAgICB9CiAgICBtYXJrZXJzLnRhYmxlID0gZG8uY2FsbChyYmluZCwgbGFwcGx5KG5hbWVzKG1hcmtlci5nZW5lcyksIAogICAgICAgIGZ1bmN0aW9uKGNlbGx0eXBlKSB7CiAgICAgICAgICAgIGdlbmVzID0gbWFya2VyLmdlbmVzW1tjZWxsdHlwZV1dCiAgICAgICAgICAgIGlmIChsZW5ndGgoZ2VuZXMpID09IDApIHsKICAgICAgICAgICAgICAgIGVyciA9IHNwcmludGYoIk5vIG1hcmtlcnMgbGVmdC5cbiIpCiAgICAgICAgICAgICAgICBzdG9wKGVyciwgY2FsbC4gPSBGQUxTRSkKICAgICAgICAgICAgfQogICAgICAgICAgICBzaWduZWQuY291bnQgPSBzdW0oc2FwcGx5KGdlbmVzLCBmdW5jdGlvbihnZW5lKSBncmVwbCgiXFwrJHwtJCIsIAogICAgICAgICAgICAgICAgZ2VuZSkpKQogICAgICAgICAgICBpcy5zaWduZWQgPSBzaWduZWQuY291bnQgPiAwCiAgICAgICAgICAgIGlmICghaXMuc2lnbmVkKSB7CiAgICAgICAgICAgICAgICBkZiA9IGRhdGEuZnJhbWUoR2VuZSA9IGdlbmVzLCBEaXJlY3Rpb24gPSArMSwgCiAgICAgICAgICAgICAgICAgIENlbGx0eXBlID0gY2VsbHR5cGUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIHBvcy5nZW5lcyA9IChhcy5jaGFyYWN0ZXIoc2FwcGx5KGdlbmVzW2dyZXBsKCIrIiwgCiAgICAgICAgICAgICAgICAgIGdlbmVzLCBmaXhlZCA9IFRSVUUpXSwgZnVuY3Rpb24oZ2VuZSkgc3RyaW5ncjo6c3RyX3JlcGxhY2UoZ2VuZSwgCiAgICAgICAgICAgICAgICAgIHN0cmluZ3I6OmZpeGVkKCIrIiksICIiKSkpKQogICAgICAgICAgICAgICAgbmVnLmdlbmVzID0gKGFzLmNoYXJhY3RlcihzYXBwbHkoZ2VuZXNbZ3JlcGwoIi0iLCAKICAgICAgICAgICAgICAgICAgZ2VuZXMsIGZpeGVkID0gVFJVRSldLCBmdW5jdGlvbihnZW5lKSBzdHJpbmdyOjpzdHJfcmVwbGFjZShnZW5lLCAKICAgICAgICAgICAgICAgICAgc3RyaW5ncjo6Zml4ZWQoIi0iKSwgIiIpKSkpCiAgICAgICAgICAgICAgICBkZiA9IGRhdGEuZnJhbWUoR2VuZSA9IGMocG9zLmdlbmVzLCBuZWcuZ2VuZXMpLCAKICAgICAgICAgICAgICAgICAgRGlyZWN0aW9uID0gYyhyZXAoKzEsIGxlbmd0aChwb3MuZ2VuZXMpKSwgcmVwKC0xLCAKICAgICAgICAgICAgICAgICAgICBsZW5ndGgobmVnLmdlbmVzKSkpLCBDZWxsdHlwZSA9IGNlbGx0eXBlLCAKICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogICAgICAgICAgICB9CiAgICAgICAgfSkpCiAgICBtYXJrZXJzLnRhYmxlID0gbWFya2Vycy50YWJsZVttYXJrZXJzLnRhYmxlJEdlbmUgJWluJSByb3duYW1lcyhzcGVjaWZpY2l0eS5wYW5lbCksIAogICAgICAgIF0KICAgIGlmIChkaW0obWFya2Vycy50YWJsZSlbMV0gPT0gMCkgewogICAgICAgIGVyciA9IHNwcmludGYoIk5vIG1hcmtlcnMgbGVmdC5cbiIpCiAgICAgICAgc3RvcChlcnIsIGNhbGwuID0gRkFMU0UpCiAgICB9CiAgICBzcGVjaWZpY2l0eS5wYW5lbCA9IHNwZWNpZmljaXR5LnBhbmVsWywgXQogICAgSURYID0gc3BsaXQoMTpkaW0obWFya2Vycy50YWJsZSlbMV0sIG1hcmtlcnMudGFibGUkQ2VsbHR5cGUpCiAgICBwcmludCgiQ29tcHV0aW5nIHNpZ25pZmljYW5jZSBzY29yZXMiKQogICAgc2V0LnNlZWQoMCkKICAgIFogPSBzYXBwbHkoSURYLCBmdW5jdGlvbihpZHgpIHsKICAgICAgICBtYXJrZXJzID0gKGFzLmNoYXJhY3RlcihtYXJrZXJzLnRhYmxlJEdlbmVbaWR4XSkpCiAgICAgICAgZGlyZWN0aW9ucyA9IG1hcmtlcnMudGFibGUkRGlyZWN0aW9uW2lkeF0KICAgICAgICBtYXNrID0gbWFya2VycyAlaW4lIGNvbG5hbWVzKHNwZWNpZmljaXR5LnBhbmVsKQogICAgICAgIEEgPSBhcy5tYXRyaXgoc3BlY2lmaWNpdHkucGFuZWxbLCBtYXJrZXJzW21hc2tdXSkKICAgICAgICBzZ24gPSBhcy5udW1lcmljKGRpcmVjdGlvbnNbbWFza10pCiAgICAgICAgc3RhdCA9IEEgJSolIHNnbgogICAgICAgIHJhbmQuc3RhdHMgPSBzYXBwbHkoMTpyYW5kLnNhbXBsZS5ubywgZnVuY3Rpb24oaSkgewogICAgICAgICAgICByYW5kLnNhbXBsZXMgPSBzYW1wbGUuaW50KGRpbShzcGVjaWZpY2l0eS5wYW5lbClbMl0sIAogICAgICAgICAgICAgICAgc3VtKG1hc2spKQogICAgICAgICAgICByYW5kLkEgPSBhcy5tYXRyaXgoc3BlY2lmaWNpdHkucGFuZWxbLCByYW5kLnNhbXBsZXNdKQogICAgICAgICAgICByYW5kLnN0YXQgPSByYW5kLkEgJSolIHNnbgogICAgICAgIH0pCiAgICAgICAgY2VsbC56c2NvcmVzID0gYXMubnVtZXJpYygoc3RhdCAtIGFwcGx5KHJhbmQuc3RhdHMsIDEsIAogICAgICAgICAgICBtZWFuKSkvYXBwbHkocmFuZC5zdGF0cywgMSwgc2QpKQogICAgICAgIHJldHVybihjZWxsLnpzY29yZXMpCiAgICB9KQogICAgWltpcy5uYShaKV0gPSAwCiAgICBMYWJlbHMgPSBjb2xuYW1lcyhaKVthcHBseShaLCAxLCB3aGljaC5tYXgpXQogICAgTGFiZWxzLmNvbmYgPSBhcHBseShaLCAxLCBtYXgpCiAgICBuYW1lcyhMYWJlbHMpID0gcm93bmFtZXMoc3BlY2lmaWNpdHkucGFuZWwpCiAgICBuYW1lcyhMYWJlbHMuY29uZikgPSByb3duYW1lcyhzcGVjaWZpY2l0eS5wYW5lbCkKICAgIHJvd25hbWVzKFopID0gcm93bmFtZXMoc3BlY2lmaWNpdHkucGFuZWwpCiAgICBvdXQgPSBsaXN0KExhYmVsID0gTGFiZWxzLCBDb25maWRlbmNlID0gTGFiZWxzLmNvbmYsIEVucmljaG1lbnQgPSBaKQogICAgcmV0dXJuKG91dCkKfQpgYGAKCmBgYHtyfQpsYXllcnMgPSByZWFkcjo6cmVhZF9yZHMoIn4vUEZDX3YzL2F1eF9kYXRhL1lhb19sYXllcl9tYXJrZXIuUkRTIikKCkRFID0gYW5ub3RhdGUucHJvZmlsZS51c2luZy5tYXJrZXJzKHQoc3BlYyksIGxheWVycykKCkhlYXRtYXAoREUkRW5yaWNobWVudFtjKDE2LCAyMSwgMjUpLCAtN10pCgoKCmBgYAoKYGBge3J9CmRhdGEoImN1cmF0ZWRNYXJrZXJzX2h1bWFuIikKY3VyYXRlZE1hcmtlcnNfaHVtYW4kQnJhaW4kUEZDJFZlbG1lc2hldjIwMTkkbWFya2VyLmdlbmVzCgpgYGAKCmBgYHtyfQpBc3QubWFya2VycyA9IGxpc3QoQXN0cm9jeXRlLklMX1ZQID0gYygiQURHUlYxIiwgIkdMSVMzIiwgIkdGQVAiLCAiQVJIR0VGNCIsICJSRlg0IiwgIlBJVFBOQzEiLCAiU09SQlMxIiwgIkRDTEsyIiwgIkRQUDEwIiwgIkFRUDQiLCAiQUJMSU0xIiwgIlJZUjMiLCAiS0NOTjMiLCAiQURDWTIiLCAiUFJLRzEiLCAiQUhDWUwxIiwgIldEUjQ5IiwgIkZBTTE4OUEyIiwgIlJORjE5QSIsICJEVE5BIiwgIk5FQkwiLCAiV1dDMSIsICJET0NLNyIsICJQQVJEMyIsICJSR01BIiwgIkNENDQiLCAiTVNJMiIsICJHUEM1IiwgIkFER1JBMyIpLCBBc3Ryb2N5dGUuUFAKPSBjKCJTTEMxQTIiLCAiQURHUlYxIiwgIkdQQzUiLCAiU0xDNEE0IiwgIlBJVFBOQzEiLCAiUllSMyIsICJaTlJGMyIsICJDQUJMRVMxIiwgIkFUUDFBMiIsICJTTEMxQTMiLCAiVFBENTJMMSIsICJUUlBNMyIsICJTRlhONSIsICJaTkY5OCIsICJDT0w1QTMiLCAiU0hST09NMyIsICJOS0FJTjMiLCAiSFBTRTIiLCAiUkZYNCIsICJHTElTMyIsICJCTVBSMUIiLCAiQUhDWUwxIiwgIkFIQ1lMMiIsICJDQVJNSUwxIiwgIlBBUkQzIiwgIk1TSTIiLCAiQ0FDSEQxIiwgIkFCTElNMSIsICJHUE02QSIpKQoKCkFzdC5ERSA9IGFubm90YXRlLnByb2ZpbGUudXNpbmcubWFya2Vycyh0KHNwZWMpLCBBc3QubWFya2VycykKCkhlYXRtYXAoQXN0LkRFJEVucmljaG1lbnQpCgpgYGAKYGBge3J9ClZlbG1lc2hldiA9IHJlYWQudGFibGUoIn4vVmVsbWVzaGV2LmNzdiIsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFQpCm1tID0gc3BsaXQoVmVsbWVzaGV2JEdlbmUubmFtZSwgVmVsbWVzaGV2JENlbGwudHlwZSkKCgpBc3QuREUgPSBhbm5vdGF0ZS5wcm9maWxlLnVzaW5nLm1hcmtlcnModChzcGVjKSwgbW0pCgpIZWF0bWFwKEFzdC5ERSRFbnJpY2htZW50KQoKCmBgYAoK